//
// Created by tang on 2020/6/17.
//

#include "gpio_utility.hpp"

#include <cstdlib> // abort()

static void delay() {
    for (int i=0; i<256; ++i)
        asm("nop");
}

DigitalPin::DigitalPin(unsigned int pin, int mode, int pull) : pin_(pin) {
    gpioSetMode(pin_, mode);
    gpioSetPullUpDown(pin_, pull);
}

void DigitalPin::operator=(bool val) const {
    gpioWrite(pin_, val ? PI_HIGH : PI_LOW);
}

DigitalPin::operator bool() const {
    return PI_HIGH == gpioRead(pin_);
}

void DigitalPin::toggle() const {
    *this = !*this;
}

PWMPin::PWMPin(unsigned int pin, unsigned int freq, unsigned int range) : pin_(pin) {
    freq_ = gpioSetPWMfrequency(pin_, freq);
    if (freq_ < 0)
        abort();
    range_ = gpioSetPWMrange(pin_, range);
    if (range_ < 0)
        abort();
}

unsigned PWMPin::setFrequency(unsigned int frequency) {
    freq_ = gpioSetPWMfrequency(pin_, frequency);
    if (freq_ < 0)
        abort();
    return freq_;
}

unsigned PWMPin::setRange(unsigned int range) {
    range_ = gpioSetPWMrange(pin_, range);
    if (range_ < 0)
        abort();
    return range_;
}

void PWMPin::operator=(double val) const {
    setDutyCycle(val);
}

void PWMPin::setDutyCycle(double duty_cycle) const {

    if (0 > duty_cycle)
        duty_cycle = 0;
    else if (duty_cycle > 1)
        duty_cycle = 1;

    auto duty = static_cast<unsigned>(duty_cycle * range_);
    gpioPWM(pin_, duty);
}

IIC::IIC(unsigned int scl, unsigned int sda, unsigned addr)
        : scl_(scl/*, PI_INPUT, PI_PUD_UP*/), sda_(sda/*, PI_INPUT, PI_PUD_UP*/), addr_(addr) {
    if (addr_ > 0x7f)
        abort();
    sda_ = true;
    scl_ = true;
}

void IIC::start() { // SCL在高电平时 SDA产生下降沿
    scl_ = true;
    delay();
    sda_ = true;
    delay();
    sda_ = false;
    delay();
    scl_ = false;
    delay();
}

void IIC::stop() { // SCL在高电平时 SDA产生上升沿
    sda_ = false;
    delay();
    scl_ = true;
    delay();
    sda_ = true;
    delay();
}

void IIC::writeByte(char byte) {
    for (int i = 7; i >= 0; --i) {
        scl_ = false; // 保持低电平写入数据
        delay();
        sda_ = static_cast<bool>(byte & (1U << i));
        delay();
        scl_ = true; // SCL上生沿从机读取数据
        delay();
    }

    // 应答检测开始
    scl_ = false;
    delay();
    sda_ = true;
    delay();
    scl_ = true;
    int j = 0;
    while (sda_ && j++ < 20) delay();
    scl_ = false;
    delay();
    // 应答检测结束
}

void IIC::write(const char *bytes, unsigned int len) {
    start();
    writeByte((addr_ << 1) | 0b0); // 发送地址加写信号
    for (unsigned i = 0U; i < len; ++i)
        writeByte(bytes[i]);
    stop();
}

void IIC::readByte(char &byte) {
    byte = 0;
    scl_ = false;
    delay();
    sda_ = true;
    delay();
    for (int i = 7; i >= 0; --i) {
        scl_ = true;
        delay();
        byte |= (static_cast<char>(sda_) << i);
        scl_ = false;
        delay();
    }
}

void IIC::read(char *bytes, unsigned int len) {
    start();
    writeByte((addr_ << 1) | 0b1); // 发送地址加写信号
    for (unsigned i = 0U; i < len; ++i)
        readByte(bytes[i]);
    stop();
}
